
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>
//#include <gc_allocator.h>

#include "CatchBlock.h"
#include "Completion.h"
#include "Object.h"
#include "Scope.h"
#include "TryStatement.h"
#include "Value.h"

TryStatement::TryStatement(Statement* block, CatchBlock* catch_block,
        Statement* final)
    : m_block(block), m_catch(catch_block), m_finally(final)
{
    // Lets not allow both catch and finally to be NULL
    assert(!(m_catch == NULL && m_finally == NULL));
}

Value* TryStatement::execute_internal(Scope* scope) const
{
    // Ecma 12.14
    // Note: this implementation merges the three constructs:
    //  try finally, try block, and try block finally, into one.
    //
    // 1. Evaluate block
    // 2. let C = that result
    Value* c = m_block->execute(scope);

    // 3. if type is not throw, go to step 6 (finally)
    if (m_catch && c->completion()->type() == CT_THROW)
    {
        // 4. Evaluate Catch with parameter c
        // > 1. Let c be the parameter that has been passed to this production
        // > 2. Create a new object as if by the expression new Object  
        Object* obj = new Object();

        // > 3. Create a property in that object whose name is identifier,
        // and value is c.value attributes are { DontDelete}.
        obj->put(m_catch->get_identifier(), c->completion()->value(),
                PA_DONT_DELETE);

        // > 4. Add the new object to the front of the scope chain.
        Scope* nscope = new Scope(scope, obj, scope->get_this());

        // > 5. Evaluate block
        Value* cblock = m_catch->get_block()->execute(nscope);

        // > 6. Remove obj from the front of the scope chain. (nothing to do)
        // > 7. Return cblock (inlined, I guess)

        // 5. if type of 4 is not normal, let C = that result
        // Here's a little hack. If we have no finally, we want to return
        // the cblock result regardless of its type
        if (!m_finally || cblock->completion()->type() != CT_NORMAL)
        {
            c = cblock;
        }
    }

    if (m_finally)
    {
        // 6. Evaluate finally
        Value* fblock = m_finally->execute(scope);

        // 7. if type of fblock is normal, return c
        // 8. Return fblock
        if (fblock->completion()->type() != CT_NORMAL)
        {
            return fblock;
        }
    }
    return c;
}
 
void TryStatement::getDeclaredVariables(std::vector<String*>& ids) const
{
    // This is very unintuitive. But I believe the ECMAscript standard
    // says blocks do not define new scopes. So a var declared in any
    // of the try/catch/finally blocks is all in the same scope?
    // I believe this is right.
    m_block->getDeclaredVariables(ids);
    if (m_catch)
    {
        m_catch->get_block()->getDeclaredVariables(ids);
    }
    if (m_finally)
    {
        m_finally->getDeclaredVariables(ids);
    }

}

